summaryrefslogtreecommitdiffstats
path: root/src/input_common/helpers/joycon_driver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/helpers/joycon_driver.cpp')
-rw-r--r--src/input_common/helpers/joycon_driver.cpp382
1 files changed, 382 insertions, 0 deletions
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 000000000..a0a2a180b
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,382 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "common/swap.h"
+#include "common/thread.h"
+#include "input_common/helpers/joycon_driver.h"
+
+namespace InputCommon::Joycon {
+JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
+ hidapi_handle = std::make_shared<JoyconHandle>();
+}
+
+JoyconDriver::~JoyconDriver() {
+ Stop();
+}
+
+void JoyconDriver::Stop() {
+ is_connected = false;
+ input_thread = {};
+}
+
+DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
+ std::scoped_lock lock{mutex};
+
+ handle_device_type = ControllerType::None;
+ GetDeviceType(device_info, handle_device_type);
+ if (handle_device_type == ControllerType::None) {
+ return DriverResult::UnsupportedControllerType;
+ }
+
+ hidapi_handle->handle =
+ SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
+ std::memcpy(&handle_serial_number, device_info->serial_number, 15);
+ if (!hidapi_handle->handle) {
+ LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
+ device_info->vendor_id, device_info->product_id);
+ return DriverResult::HandleInUse;
+ }
+ SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
+ return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::InitializeDevice() {
+ if (!hidapi_handle->handle) {
+ return DriverResult::InvalidHandle;
+ }
+ std::scoped_lock lock{mutex};
+ disable_input_thread = true;
+
+ // Reset Counters
+ error_counter = 0;
+ hidapi_handle->packet_counter = 0;
+
+ // Set HW default configuration
+ vibration_enabled = true;
+ motion_enabled = true;
+ hidbus_enabled = false;
+ nfc_enabled = false;
+ passive_enabled = false;
+ gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
+ gyro_performance = Joycon::GyroPerformance::HZ833;
+ accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
+ accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
+
+ // Initialize HW Protocols
+
+ // Get fixed joycon info
+ supported_features = GetSupportedFeatures();
+
+ // Get Calibration data
+
+ // Set led status
+
+ // Apply HW configuration
+ SetPollingMode();
+
+ // Start pooling for data
+ is_connected = true;
+ if (!input_thread_running) {
+ input_thread =
+ std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
+ }
+
+ disable_input_thread = false;
+ return DriverResult::Success;
+}
+
+void JoyconDriver::InputThread(std::stop_token stop_token) {
+ LOG_INFO(Input, "JC Adapter input thread started");
+ Common::SetCurrentThreadName("JoyconInput");
+ input_thread_running = true;
+
+ // Max update rate is 5ms, ensure we are always able to read a bit faster
+ constexpr int ThreadDelay = 2;
+ std::vector<u8> buffer(MaxBufferSize);
+
+ while (!stop_token.stop_requested()) {
+ int status = 0;
+
+ if (!IsInputThreadValid()) {
+ input_thread.request_stop();
+ continue;
+ }
+
+ // By disabling the input thread we can ensure custom commands will succeed as no package is
+ // skipped
+ if (!disable_input_thread) {
+ status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
+ ThreadDelay);
+ } else {
+ std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
+ }
+
+ if (IsPayloadCorrect(status, buffer)) {
+ OnNewData(buffer);
+ }
+
+ std::this_thread::yield();
+ }
+
+ is_connected = false;
+ input_thread_running = false;
+ LOG_INFO(Input, "JC Adapter input thread stopped");
+}
+
+void JoyconDriver::OnNewData(std::span<u8> buffer) {
+ const auto report_mode = static_cast<InputReport>(buffer[0]);
+
+ switch (report_mode) {
+ case InputReport::STANDARD_FULL_60HZ:
+ ReadActiveMode(buffer);
+ break;
+ case InputReport::NFC_IR_MODE_60HZ:
+ ReadNfcIRMode(buffer);
+ break;
+ case InputReport::SIMPLE_HID_MODE:
+ ReadPassiveMode(buffer);
+ break;
+ default:
+ LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
+ break;
+ }
+}
+
+void JoyconDriver::SetPollingMode() {
+ disable_input_thread = true;
+ disable_input_thread = false;
+}
+
+JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
+ SupportedFeatures features{
+ .passive = true,
+ .motion = true,
+ .vibration = true,
+ };
+
+ if (device_type == ControllerType::Right) {
+ features.nfc = true;
+ features.irs = true;
+ features.hidbus = true;
+ }
+
+ if (device_type == ControllerType::Pro) {
+ features.nfc = true;
+ }
+ return features;
+}
+
+void JoyconDriver::ReadActiveMode(std::span<u8> buffer) {
+ InputReportActive data{};
+ memcpy(&data, buffer.data(), sizeof(InputReportActive));
+
+ // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
+ // experience
+ const auto now = std::chrono::steady_clock::now();
+ const auto new_delta_time =
+ std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count();
+ delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2));
+ last_update = now;
+
+ switch (device_type) {
+ case Joycon::ControllerType::Left:
+ break;
+ case Joycon::ControllerType::Right:
+ break;
+ case Joycon::ControllerType::Pro:
+ break;
+ case Joycon::ControllerType::Grip:
+ case Joycon::ControllerType::Dual:
+ case Joycon::ControllerType::None:
+ break;
+ }
+
+ on_battery_data(data.battery_status);
+ on_color_data(color);
+}
+
+void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) {
+ InputReportPassive data{};
+ memcpy(&data, buffer.data(), sizeof(InputReportPassive));
+
+ switch (device_type) {
+ case Joycon::ControllerType::Left:
+ break;
+ case Joycon::ControllerType::Right:
+ break;
+ case Joycon::ControllerType::Pro:
+ break;
+ case Joycon::ControllerType::Grip:
+ case Joycon::ControllerType::Dual:
+ case Joycon::ControllerType::None:
+ break;
+ }
+}
+
+void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) {
+ // This mode is compatible with the active mode
+ ReadActiveMode(buffer);
+
+ if (!nfc_enabled) {
+ return;
+ }
+}
+
+bool JoyconDriver::IsInputThreadValid() const {
+ if (!is_connected) {
+ return false;
+ }
+ if (hidapi_handle->handle == nullptr) {
+ return false;
+ }
+ // Controller is not responding. Terminate connection
+ if (error_counter > MaxErrorCount) {
+ return false;
+ }
+ return true;
+}
+
+bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
+ if (status <= -1) {
+ error_counter++;
+ return false;
+ }
+ // There's no new data
+ if (status == 0) {
+ return false;
+ }
+ // No reply ever starts with zero
+ if (buffer[0] == 0x00) {
+ error_counter++;
+ return false;
+ }
+ error_counter = 0;
+ return true;
+}
+
+DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
+ std::scoped_lock lock{mutex};
+ return DriverResult::NotSupported;
+}
+
+DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
+ std::scoped_lock lock{mutex};
+ return DriverResult::NotSupported;
+}
+
+DriverResult JoyconDriver::SetPasiveMode() {
+ motion_enabled = false;
+ hidbus_enabled = false;
+ nfc_enabled = false;
+ passive_enabled = true;
+ SetPollingMode();
+ return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::SetActiveMode() {
+ motion_enabled = false;
+ hidbus_enabled = false;
+ nfc_enabled = false;
+ passive_enabled = false;
+ SetPollingMode();
+ return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::SetNfcMode() {
+ motion_enabled = false;
+ hidbus_enabled = false;
+ nfc_enabled = true;
+ passive_enabled = false;
+ SetPollingMode();
+ return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::SetRingConMode() {
+ motion_enabled = true;
+ hidbus_enabled = true;
+ nfc_enabled = false;
+ passive_enabled = false;
+ SetPollingMode();
+ return DriverResult::Success;
+}
+
+bool JoyconDriver::IsConnected() const {
+ std::scoped_lock lock{mutex};
+ return is_connected;
+}
+
+bool JoyconDriver::IsVibrationEnabled() const {
+ std::scoped_lock lock{mutex};
+ return vibration_enabled;
+}
+
+FirmwareVersion JoyconDriver::GetDeviceVersion() const {
+ std::scoped_lock lock{mutex};
+ return version;
+}
+
+Color JoyconDriver::GetDeviceColor() const {
+ std::scoped_lock lock{mutex};
+ return color;
+}
+
+std::size_t JoyconDriver::GetDevicePort() const {
+ std::scoped_lock lock{mutex};
+ return port;
+}
+
+ControllerType JoyconDriver::GetDeviceType() const {
+ std::scoped_lock lock{mutex};
+ return handle_device_type;
+}
+
+ControllerType JoyconDriver::GetHandleDeviceType() const {
+ std::scoped_lock lock{mutex};
+ return handle_device_type;
+}
+
+SerialNumber JoyconDriver::GetSerialNumber() const {
+ std::scoped_lock lock{mutex};
+ return serial_number;
+}
+
+SerialNumber JoyconDriver::GetHandleSerialNumber() const {
+ std::scoped_lock lock{mutex};
+ return handle_serial_number;
+}
+
+Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
+ ControllerType& controller_type) {
+ std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
+ std::pair<u32, Joycon::ControllerType>{0x2006, Joycon::ControllerType::Left},
+ {0x2007, Joycon::ControllerType::Right},
+ {0x2009, Joycon::ControllerType::Pro},
+ {0x200E, Joycon::ControllerType::Grip},
+ };
+ constexpr u16 nintendo_vendor_id = 0x057e;
+
+ controller_type = Joycon::ControllerType::None;
+ if (device_info->vendor_id != nintendo_vendor_id) {
+ return Joycon::DriverResult::UnsupportedControllerType;
+ }
+
+ for (const auto& [product_id, type] : supported_devices) {
+ if (device_info->product_id == static_cast<u16>(product_id)) {
+ controller_type = type;
+ return Joycon::DriverResult::Success;
+ }
+ }
+ return Joycon::DriverResult::UnsupportedControllerType;
+}
+
+Joycon::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
+ Joycon::SerialNumber& serial_number) {
+ if (device_info->serial_number == nullptr) {
+ return Joycon::DriverResult::Unknown;
+ }
+ std::memcpy(&serial_number, device_info->serial_number, 15);
+ return Joycon::DriverResult::Success;
+}
+
+} // namespace InputCommon::Joycon